/*
    socklibpp library
    Copyright (C) 2005  Daniel K. O. <danielosmari at users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/



/**     \file sockbuf.h
*/


#ifndef SOCKBUF_H
#define SOCKBUF_H



#include <istream>
#include <ostream>
#include <new>
#include <streambuf>
#include <memory>

#include "sockbase.h"

namespace socklib {


/* TODO (DanielKO#1#): Replace the exceptions by socket-specific ones */



/**     \brief basic_sockbuf template

        This class is intended for connection-oriented sockets (sock_stream)
        It doesn't provide (yet) access to advanced sock_stream sockets handling
        (like OOB) because this is usally low-level.

        It's not (currently) intended for non-blocking sockets.

        \sa socklib::base
*/
template<typename _CharT, typename _Traits = std::char_traits<_CharT> >
class basic_sockbuf : public std::basic_streambuf<_CharT, _Traits>
{
public:
        // Types:
        typedef _CharT                          char_type;
        typedef _Traits                         traits_type;
        typedef typename traits_type::int_type  int_type;
        typedef typename traits_type::pos_type  pos_type;
        typedef typename traits_type::off_type  off_type;

        typedef std::basic_streambuf<char_type, traits_type> __streambuf_type;

protected:

        /// The socket
        base _M_socket;

        /// The input and output buffers
        char *_M_ibuffer, *_M_obuffer;

        /// The size of input and output buffers
        int _M_buf_size;


public:
        /// Default constructor
        basic_sockbuf() :
                _M_socket(base::invalid_socket)
        {
                _M_buffer_init();
        }

        /**     \brief Constructor taking a socklib::base argument
        
                \note   basic_sockbuf will now own the socklib::base object,
                        and will be responsible for opening and closing it.
        */
        explicit
        basic_sockbuf(base __sock_base)
        :       _M_socket(__sock_base)
        {
                _M_buffer_init();
        }
        

        /// Convenience constructor, that establishes a connection to a remote host
        explicit
        basic_sockbuf(const addr_in& _sin)
        :       _M_socket(base::invalid_socket)
        {
                _M_buffer_init();
                connect(_sin);
        }
        

        /// Destructor that calls sync(), close() and deletes the buffer
        virtual
        ~basic_sockbuf()
        {
                sync();
                close();
                _M_buffer_delete();
        }

        /// Get the socket
        base
        socket() const
        throw()
        {
	       return _M_socket;
        }


        /// Set the socket
        void
        socket(base __sock)
        throw()
        {
                _M_socket = __sock;
        }
        
        
        /**     \brief Establish a connection
                \return true if the connection has been established
        */
        bool
        connect(const addr_in& _sin)
        throw ()
        {
                if (_M_socket.fd == base::invalid_socket)
                        if (!_M_socket.create(  base::pf_inet,
                                                base::sock_stream,
                                                base::proto_ip))
                                return false;
                                                
                return _M_socket.connect(_sin);
        }
        
        
        /**     \brief Establish a connection
                \param _addr    The host name or ip
                \param _port    The host port
                \return true if the connection has been established
        */
        bool
        connect(const std::string& _addr, uint16_t _port)
        throw()
        {
                addr_in _sin;

                if (!_sin.ip(_addr))
                        if (!_sin.resolve(_addr))
                                return false;

                _sin.port(_port);

                return connect(_sin);
        }


        /// Close the connection and release resources allocated by the socket
        basic_sockbuf*
        close()
        throw()
        {
                bool _s = _M_socket.close();
                _M_socket.fd = base::invalid_socket;

                return  _s ? 0 : this;
        }
        

        /**     \brief Check if the connection has data to be read.
                \param _usec    microseconds to wait until the socket can be
                                read.
                \param _sec     seconds to wait until the socket can be read.
                                Set to -1 if you want it to block.
                \throw ios_base::failure if an error occurred.
                \sa socklib::base::can_read()
        */
        bool can_read(uint32_t _usec=0, uint32_t _sec = 0)
        throw (std::ios_base::failure)
        {
                if (_M_socket.can_read(_usec, _sec))
                        return true;
                if (_M_socket.error)
                        throw std::ios_base::failure("basic_sockbuf::can_read()");
                return false;
        }
        

        /**     \brief Check if the connection has space to write data.
                \param _usec    microseconds to wait until the socket can be
                                written.
                \param _sec     seconds to wait until the socket can be written.
                                Set to -1 if you want it to block.
                \throw ios_base::failure if an error occurred.
                \sa socklib::base::can_write()
        */
        bool can_write(uint32_t _usec=0, uint32_t _sec = 0)
        throw (std::ios_base::failure)
        {
                if (_M_socket.can_write(_usec, _sec))
                        return true;
                if (_M_socket.error)
                        throw std::ios_base::failure("basic_sockbuf::can_write");
                return false;
        }
        

        /// Checks to see if the socket is connected
        bool is_connected() const
        throw()
        {
                addr_in _sin;
                if (!_M_socket.peername(_sin))
                        return false;
                return true;
        }

protected:
        // Common initialization code for both ctors goes here.
        void _M_buffer_init(int _sz = 1024)
        {
                _M_buf_size = _sz;
                _M_ibuffer = new char_type[ (_M_buf_size+1) * 2 ];
                _M_obuffer = _M_ibuffer + _M_buf_size + 1;

                this->setg(_M_ibuffer, _M_ibuffer + 1, _M_ibuffer + 1);
                this->setp(_M_obuffer, _M_obuffer + _M_buf_size);
        }
        
        void _M_buffer_delete()
        {
                delete[] _M_ibuffer;
        }


        /// Called when the input buffer is empty.
        virtual int_type
        underflow()
        {
                *this->eback() = -1[this->gptr()];  // to allow putback operations

                int result = _M_socket.recv(this->eback() + 1 , _M_buf_size);

                if (result < 1)
                        return traits_type::eof();

                this->setg(this->eback(), this->eback()+1, this->eback() + result + 1);
                
                return traits_type::to_int_type(*this->gptr());
        }
        

        /// Called to test how many bytes can be read in the next operation.
        virtual int
        showmanyc()
        {
                unsigned long data;
                if (!_M_socket.ioctl(base::fionread, data))
                        return -1;
                return data;
        }
        

        /// Called when the output buffer is full.
        virtual int_type
        overflow(int_type __c = traits_type::eof())
        {
                char *s1 = this->pbase();
                char *s2 = this->pptr();
                
                int num = s2-s1;

                /* TODO (DanielKO#2#): Make overflow use socklib::base::sendall() */
                if (__c != traits_type::eof()) {
                        *this->pptr() = __c;
                        ++num;
                }
                
                if (!num)
                        return traits_type::eof();

                int result = _M_socket.send(s1, num);

                if (_M_socket.error)
                        return traits_type::eof();

                if (result != num)      // didn't write everything
                        std::memmove(
                                s1,
                                s1 + result,
                                num - result);

                this->setp(_M_obuffer, _M_obuffer + _M_buf_size);

                return traits_type::to_int_type(num);
        }


        /// Send everything that is on the output buffer.
        virtual int
        sync()
        {
                while (this->pptr() > this->pbase())
                        if (overflow(traits_type::eof()) == traits_type::eof())
                                return -1;
                                
                return 0;
        }


        /**     \brief Sets the internal buffers to size n

                Can't use external buffers; both output and input are the same
                size. Doesn't support unbuffered input/output.
                
                \param _n       The size of the internal input and output buffer.
                                Both will have the size _n each, so they sum up _n*2
                                bytes.
        */
        /* TODO (DanielKO#2#): Improve setbuf */
        virtual __streambuf_type*
        setbuf(char_type*, std::streamsize _n)
        {
                if (_n<2)
                        return 0;
                try {
                        _M_buffer_delete();
                        _M_buffer_init(_n);
                        return this;
                }
                catch(...)
                {
                        return 0;
                }
        }

/* TODO (DanielKO#4#): Implement xsputn if the current default 
                       inherited implementation is slow. (Need to make some 
                       performance tests) */



};


/// The default basic_sockbuf instantiation
typedef basic_sockbuf<char>     sockbuf;

}

#endif
